/*
 * Copyright 2008 Wink Saville
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.lang.Thread;
import java.io.*;
import java.util.ArrayList;
import junit.framework.Assert;

import com.saville.debug.*;
import com.saville.serdes.*;
import com.saville.mc.*;
import com.saville.msgcomp.*;

/*
 * Test Mc's.
 * 
 * This is an McActivity it extends McActivity, calls super.onCreate(icicle,
 * "name") and implements processMsg.
 * 
 * It also tests various forms of Mcs.
 */

public class TestMc extends ActiveMc {

    public TestMc() {
        super("TestMc");
        df = Log.VERBOSE;

        test();

        Log.print("TestMc() done");
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new TestMc();
    }

    /*
     * Process our asynchronous messages
     * 
     * @see com.saville.android.mc.IMc#processMsg(com.saville.android.mc.Msg)
     */
    public void processMsg(Msg msg) {
        Log.v(df, "%s: processMsg() tid=%d cmd=%d", mName, Thread.currentThread().getId(), msg.cmd);

        if (msg.guid == 1) {
            Log.v(df, "%s: processMsg() keyCode=%d", mName, msg.arg1);
        }

    }

    /*
     * Run the tests
     */
    void test() {
        testSerialization();

        /*
         * Test url lookup
         */
        String uri = "amc://127.0.0.1/" + mMcMgr.getName();
        int id1 = lookup(uri);
        Assert.assertFalse("TestMc: error expected sucessful lookup of " + uri, id1 == -1);
        int id2 = lookup(uri);
        Assert.assertFalse("TestMc: error expected sucessful lookup of " + uri, id2 == -1);
        Assert.assertEquals("TestMc: error expected two lookups to be the same", id1, id2);

        /*
         * This lookup should fail
         */
        int id = lookup("fail");
        Assert.assertEquals("TestMc: error expected lookup(\"fail\") to FAIL", id, -1);

        /*
         * This lookup should succeed and it must be 0
         */
        id = lookup(mMcMgr.getName());
        Log.print("testLookup: mMcMgr name=%s id=%d", mMcMgr.getName(), id);
        Assert.assertEquals("TestMc: error expected lookup(mMcMgr.getName()) to SUCCEED", id, 0);

        /*
         * We've created several Mcs, each Mc will receive a MC_CMD_STARTED msg
         * as the first message. They will then receive the second message the
         * we send from here. Finally every time onKeyDown called each will
         * receive another message.
         */
        mOurMcIds = new ArrayList<Integer>();

        /*
         * Send ourselves a message
         */
        mOurMcIds.add(mMcId.id);
        Msg msg = getMsgForSelf(0, -1);
        sendMsg(msg);

        /*
         * Test an Mc inside another thread
         */
        tMc1 tmc1 = new tMc1("tMc1");
        mOurMcIds.add(lookup("tMc1"));
        msg = getMsg(mOurMcIds.get(1), 0, 1);
        sendMsg(msg);

        /*
         * Test an Mc as part of a runnable
         */
        tMc2 tmc2 = new tMc2("tMc2");
        new Thread(tmc2).start();
        tmc2.waitTillRunning();
        mOurMcIds.add(lookup("tMc2"));
        msg = getMsg(mOurMcIds.get(2), 0, 2);
        sendMsg(msg);

        /*
         * Test ActiveMc
         */
        TestMcActiveMc testMcActiveMc = new TestMcActiveMc("TestMcActiveMc");
        mOurMcIds.add(lookup("TestMcActiveMc"));
        msg = getMsg(mOurMcIds.get(3), 0, 3);
        sendMsg(msg);

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }

        /*
         * Try stop
         */
        testMcActiveMc.stopProcessing();
        tmc1.stopProcessing();
        tmc2.stopProcessing();
        stopProcessing();
    }

    void testSerialization() {

        try {
            /* Test String serialization as part of m.obj */
            Msg m = getMsg();
            m.obj = new StringSer("hi");
            SerdesBinary s = new SerdesBinary();
            m.serialize(s);
            byte b[] = s.data();
            s.init(b, 0, s.size());
            Msg xm = (Msg) s.readObject();
            String str = (String) xm.obj;
            Assert.assertEquals(str, "hi");

            m = getMsg();
            McId mcId = new McId();
            mcId.name = "yo";
            mcId.ipAddr = "127.0.0.1";
            mcId.ipPort = 47047;
            mcId.id = 12345;
            m.obj = mcId;
            s.init();
            m.serialize(s);
            b = s.data();
            s.init(b, 0, s.size());
            xm = (Msg) s.readObject();
            mcId = (McId) xm.obj;
            Assert.assertEquals(mcId.name, "yo");
            Assert.assertEquals(mcId.ipAddr, "127.0.0.1");
            Assert.assertEquals(mcId.ipPort, 47047);
            Assert.assertEquals(mcId.id, 12345);

            // byte dataIn[] = new byte[10];
            byte data[] = new byte[100];
            for (int i = 0; i < data.length; i++) {
                data[i] = (byte) (i & 0xff);
            }
            byte data1[] = new byte[100];
            for (int i = 0; i < data1.length; i++) {
                data1[i] = (byte) (i & 0xff);
            }

            int len;
            long st = Timing.readTsc();
            tArray(data1);
            double ds = Timing.tscToSecs(Timing.readTsc() - st);
            Log.print("First ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);

            st = Timing.readTsc();
            len = tArray(data);
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            Log.print("data len=%d ds=%f rate=%f/s time/per=%f", len, ds, 1.0 / ds, ds);

            int count = 10;
            long durs[] = new long[count];

            st = Timing.readTsc();
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                len = tMsg(m);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            Log.print("AVG Msg len=%d ds=%f rate=%f/s time/per=%f", len, ds, 1.0 / ds, ds);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("               ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            st = Timing.readTsc();
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tArray(data);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            double rate = count / ds;
            Log.print("AVG data ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }
            Log.print("done test");
        } catch (Exception e) {
        }
    }

    int tMsg(Msg m) throws Exception {
        SerdesBinary s = new SerdesBinary();

        m.serialize(s);
        byte idata[] = s.data();
        int len = s.size();
        if (SERIALIZE_DEBUG) {
            Log.print("idata.length=%d s.size=%d", idata.length, len);
        }

        s.init(idata, 0, len);
        if (SERIALIZE_DEBUG) {
            Msg xm = (Msg) s.readObject();
            Log.print("tMsg: xm=%s", xm);
        } else {
            s.readObject();
        }

        return len;
    }

    int tArray(byte data[]) throws Exception {
        ByteArrayOutputStream omb = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(omb);

        oos.writeObject(data);
        oos.close();
        if (SERIALIZE_DEBUG)
            Log.print("omb.size=%d", omb.size());

        byte idata[] = omb.toByteArray();
        if (SERIALIZE_DEBUG)
            Log.print("omb.size=%d idata.length=%d", omb.size(), idata.length);

        ByteArrayInputStream imb = new ByteArrayInputStream(idata);
        ObjectInputStream ois = new ObjectInputStream(imb);
        if (SERIALIZE_DEBUG) {
            byte xdata[] = (byte[]) ois.readObject();
            Log.print("xdata.length=%d", xdata.length);
        } else {
            ois.readObject();
        }

        return idata.length;
    }

    /*
     * Example of embedding an Mc in a class that extends Thread.
     */
    private class tMc1 extends Thread {
        tMc1(String name) {
            mClassName = name;

            Thread thrd = new Thread() {
                public void run() {
                    MsgPump.prepare();
                    mLooper = MsgPump.myMsgPump();
                    mMyMc = new MyMc(mClassName);
                    mRunning = true;
                    MsgPump.loop();
                    Log.print("tMc1: has stopped");
                }
            };
            thrd.start();

            while (!mRunning) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
            }
        }

        public void stopProcessing() {
            mLooper.stopProcessing();
        }

        class MyMc extends Mc {
            MyMc(String name) {
                super(name);
                df = Log.VERBOSE;
            }

            public void processMsg(Msg msg) {
                Log.v(df, "%s: tid=%d cmd=%d", mName, Thread.currentThread().getId(), msg.cmd);

                if (msg.guid == 1) {
                    Log.v(df, "%s: keyCode=%d", mName, msg.arg1);
                }
            }
        }

        volatile boolean mRunning;
        String mClassName;
        MsgPump mLooper;
        Mc mMyMc;
    }

    /*
     * Example of an Mc that can be passed as a Runnable parameter
     */
    private class tMc2 extends Mc implements Runnable {
        tMc2(String name) {
            mName = name;
            df = Log.VERBOSE;
        }

        public void waitTillRunning() {
            while (!mRunning) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
        }

        public void stopProcessing() {
            mLooper.stopProcessing();
        }

        public void run() {
            MsgPump.prepare();
            mLooper = MsgPump.myMsgPump();
            init(mName, mLooper, true);
            mRunning = true;
            MsgPump.loop();
            Log.print("tMc2: has stopped");
        }

        public void processMsg(Msg msg) {
            Log.v(df, "%s: processMsg() tid=%d cmd=%d", mName, Thread.currentThread().getId(),
                msg.cmd);

            if (msg.guid == 1) {
                Log.v(df, "%s: processMsg() keyCode=%d", mName, msg.arg1);
            }
        }

        MsgPump mLooper;
        volatile boolean mRunning;
    }

    /*
     * Example of an ActiveMc.
     */
    private class TestMcActiveMc extends ActiveMc {
        public TestMcActiveMc(String name) {
            super(name);
            df = Log.VERBOSE;
        }

        public void processMsg(Msg msg) {
            Log.v(df, "%s: processMsg() tid=%d cmd=%d", mName, Thread.currentThread().getId(),
                msg.cmd);

            if (msg.guid == 1) {
                Log.v(df, "%s: processMsg() keyCode=%d", mName, msg.arg1);
            }
        }
    }

    java.util.ArrayList<Integer> mOurMcIds;

    private static final boolean SERIALIZE_DEBUG = false;
}
